/*******************************************************************************
 * This file is part of Crunch Graphics.
 *
 * Crunch Graphics is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * Crunch Graphics is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * Crunch Graphics.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package com.crunch.graphics.gpu;

import javax.media.opengl.GL;
import javax.media.opengl.GL3;
import javax.media.opengl.GL4;

/** Keeps track of the state on the GPU.
 */
public class GpuState {
	/** Initializes all states to the current GPU state.
	 */
	public void initialize() {
		// initialize all states to their current states
		blendState = createBlendState();
		blendState.getCurrentState();

		depthStencilState = createDepthStencilState();
		depthStencilState.getCurrentState();

		rasterizerState = createRasterizerState();
		rasterizerState.getCurrentState();

		scissorState = createScissorState();
		scissorState.getCurrentState();

		viewportState = createViewportState();
		viewportState.getCurrentState();

		int[] maxDrawBuffersResult = new int[1];
		getGl().glGetIntegerv(GL3.GL_MAX_DRAW_BUFFERS, maxDrawBuffersResult, 0);
		maxDrawBuffers = Math.min(maxDrawBuffersResult[0], GpuDrawBufferState.MAX_DRAW_BUFFERS);
		drawBufferState = createDrawBufferState();
		drawBufferState.getCurrentState();

		int[] textureImageUnitCount = new int[1];
		getGl().glGetIntegerv(GL3.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, textureImageUnitCount, 0);
		textureImageUnits = new GpuTextureImageUnit[textureImageUnitCount[0]];
		for (int i = 0; i < textureImageUnits.length; ++i) {
			textureImageUnits[i] = new GpuTextureImageUnit(this, i);
			textureImageUnits[i].acquire();
		}
		int[] activeTextureImageUnitResult = new int[1];
		getGl().glGetIntegerv(GL.GL_ACTIVE_TEXTURE, activeTextureImageUnitResult, 0);
		activeTextureImageUnit = activeTextureImageUnitResult[0] - GL.GL_TEXTURE0;

		int[] maxColorAttachmentsResult = new int[1];
		getGl().glGetIntegerv(GL3.GL_MAX_COLOR_ATTACHMENTS, maxColorAttachmentsResult, 0);
		maxColorAttachments = maxColorAttachmentsResult[0];

		framebufferSRgbEnabled = getGl().glIsEnabled(GL3.GL_FRAMEBUFFER_SRGB);

		int[] maxAttributesResult = new int[1];
		getGl().glGetIntegerv(GL3.GL_MAX_VERTEX_ATTRIBS, maxAttributesResult, 0);
		attributes = new GpuAttribute[maxAttributesResult[0]];
		for (int i = 0; i < attributes.length; ++i) {
			attributes[i] = new GpuAttribute(this, i);
			attributes[i].acquire();
		}

		drawState = createDrawState();
		drawState.getCurrentState();
	}

	public void terminate() {
		// release bound textures and texture image units
		for (int i = 0; i < textureImageUnits.length; ++i) {
			GpuTextureImageUnit tiu = textureImageUnits[i];
			tiu.unbindTexture(GL3.GL_TEXTURE_1D);
			tiu.unbindTexture(GL3.GL_TEXTURE_1D_ARRAY);
			tiu.unbindTexture(GL.GL_TEXTURE_2D);
			tiu.unbindTexture(GL3.GL_TEXTURE_2D_ARRAY);
			tiu.unbindTexture(GL3.GL_TEXTURE_2D_MULTISAMPLE);
			tiu.unbindTexture(GL3.GL_TEXTURE_2D_MULTISAMPLE_ARRAY);
			tiu.unbindTexture(GL3.GL_TEXTURE_3D);
			tiu.unbindTexture(GL3.GL_TEXTURE_BUFFER);
			tiu.unbindTexture(GL.GL_TEXTURE_CUBE_MAP);
			tiu.unbindTexture(GL4.GL_TEXTURE_CUBE_MAP_ARRAY);
			tiu.unbindTexture(GL3.GL_TEXTURE_RECTANGLE);
			tiu.release();
			textureImageUnits[i] = null;
		}

		// unbind renderbuffer
		unbindRenderbuffer();

		// unbind framebuffer
		unbindFramebuffer(GL.GL_FRAMEBUFFER);

		// unbind buffers
		unbindBuffer(GL.GL_ARRAY_BUFFER);
		unbindBuffer(GL3.GL_COPY_READ_BUFFER);
		unbindBuffer(GL3.GL_COPY_WRITE_BUFFER);
		unbindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER);
		unbindBuffer(GL3.GL_PIXEL_PACK_BUFFER);
		unbindBuffer(GL3.GL_PIXEL_UNPACK_BUFFER);
		unbindBuffer(GL3.GL_TEXTURE_BUFFER);
		unbindBuffer(GL3.GL_TRANSFORM_FEEDBACK_BUFFER);
		unbindBuffer(GL3.GL_UNIFORM_BUFFER);

		// unbind program
		unbindProgram();

		// release attributes
		for (int i = 0; i < attributes.length; ++i) {
			attributes[i].release();
			attributes[i] = null;
		}
	}

	/** Sets the OpenGL state object.
	 */
	public void setGl(GL3 gl) {
		this.gl = gl;
	}

	/** Returns the OpenGL state object.
	 */
	public GL3 getGl() {
		return gl;
	}

	private GL3 gl;

	// BASIC STATE:

	/** Creates a blend state.
	 */
	public GpuBlendState createBlendState() {
		return new GpuBlendState(this);
	}

	/** Binds the blend state.
	 */
	public void bind(GpuBlendState newState) {
		newState.apply(blendState);
	}

	/** Retrieves the current blend state.
	 */
	public GpuBlendState getCurrentBlendState() {
		return new GpuBlendState(blendState);
	}

	/** Creates a depth/stencil state.
	 */
	public GpuDepthStencilState createDepthStencilState() {
		return new GpuDepthStencilState(this);
	}

	/** Binds the depth/stencil state.
	 */
	public void bind(GpuDepthStencilState newState) {
		newState.apply(depthStencilState);
	}

	/** Retrieves the current depth/stencil state.
	 */
	public GpuDepthStencilState getCurrentDepthStencilState() {
		return new GpuDepthStencilState(depthStencilState);
	}

	/** Creates a rasterizer state.
	 */
	public GpuRasterizerState createRasterizerState() {
		return new GpuRasterizerState(this);
	}

	/** Binds the rasterizer state.
	 */
	public void bind(GpuRasterizerState newState) {
		newState.apply(rasterizerState);
	}

	/** Retrieves the current rasterizer state.
	 */
	public GpuRasterizerState getCurrentRasterizerState() {
		return new GpuRasterizerState(rasterizerState);
	}

	/** Creates a scissor state.
	 */
	public GpuScissorState createScissorState() {
		return new GpuScissorState(this);
	}

	/** Binds the scissor state.
	 */
	public void bind(GpuScissorState newState) {
		newState.apply(scissorState);
	}

	/** Retrieves the current scissor state.
	 */
	public GpuScissorState getCurrentScissorState() {
		return new GpuScissorState(scissorState);
	}

	/** Creates a viewport state.
	 */
	public GpuViewportState createViewportState() {
		return new GpuViewportState(this);
	}

	/** Binds the viewport state.
	 */
	public void bind(GpuViewportState newState) {
		newState.apply(viewportState);
	}

	/** Retrieves the current viewport state.
	 */
	public GpuViewportState getCurrentViewportState() {
		return new GpuViewportState(viewportState);
	}

	/** Returns the maximum number of draw buffers.
	 */
	public int getMaxDrawBuffers() {
		return maxDrawBuffers;
	}

	/** Creates a draw buffer state.
	 */
	public GpuDrawBufferState createDrawBufferState() {
		return new GpuDrawBufferState(this);
	}

	/** Binds the draw buffer state.
	 */
	public void bind(GpuDrawBufferState newState) {
		newState.apply(drawBufferState);
	}

	/** Retrieves the current draw buffer state.
	 */
	public GpuDrawBufferState getCurrentDrawBufferState() {
		return new GpuDrawBufferState(drawBufferState);
	}

	// TEXTURES:

	/** Creates a 1D texture.
	 */
	public GpuTexture createTexture(GpuTexture.Texture1DDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a 1D array texture.
	 */
	public GpuTexture createTexture(GpuTexture.Texture1DArrayDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a 2D texture.
	 */
	public GpuTexture createTexture(GpuTexture.Texture2DDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a 2D array texture.
	 */
	public GpuTexture createTexture(GpuTexture.Texture2DArrayDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a 2D multisample texture.
	 */
	public GpuTexture createTexture(GpuTexture.Texture2DMultisampleDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a 2D multisample array texture.
	 */
	public GpuTexture createTexture(GpuTexture.Texture2DMultisampleArrayDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a 3D texture.
	 */
	public GpuTexture createTexture(GpuTexture.Texture3DDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a buffer texture.
	 */
	public GpuTexture createTexture(GpuTexture.TextureBufferDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a cube map texture.
	 */
	public GpuTexture createTexture(GpuTexture.TextureCubeMapDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Creates a rectangle texture.
	 */
	public GpuTexture createTexture(GpuTexture.TextureRectangleDescription desc) {
		GpuTexture ret = new GpuTexture(this, desc);
		ret.acquire();
		return ret;
	}

	/** Returns the number of available texture image units.
	 */
	public int getTextureImageUnitCount() {
		return textureImageUnits.length;
	}

	/** Sets the index of the active texture image unit.
	 */
	public void setActiveTextureImageUnitIndex(int i) {
		if (i != activeTextureImageUnit) {
			getGl().glActiveTexture(GL.GL_TEXTURE0 + i);
			activeTextureImageUnit = i;
		}
	}

	/** Returns the index of the active texture image unit.
	 */
	public int getActiveTextureImageUnitIndex() {
		return activeTextureImageUnit;
	}

	/** Returns the given texture image unit.
	 */
	public GpuTextureImageUnit getTextureImageUnit(int i) {
		return textureImageUnits[i];
	}

	/** Returns the given texture image unit.
	 */
	public GpuTextureImageUnit getActiveTextureImageUnit() {
		return textureImageUnits[activeTextureImageUnit];
	}

	// RENDERBUFFERS:

	/** Creates a renderbuffer.
	 */
	public GpuRenderbuffer createRenderbuffer(GpuRenderbuffer.RenderbufferDescription desc) {
		GpuRenderbuffer ret = new GpuRenderbuffer(this, desc);
		ret.acquire();
		return ret;
	}

	/** Binds the given renderbuffer.
	 */
	public void bindRenderbuffer(GpuRenderbuffer renderbuffer) {
		if (renderbufferBinding == renderbuffer) {
			// already bound
			return;
		}

		getGl().glBindRenderbuffer(GL.GL_RENDERBUFFER, renderbuffer.getId());

		renderbuffer.acquire();
		if (renderbufferBinding != null) {
			renderbufferBinding.release();
		}
		renderbufferBinding = renderbuffer;
	}

	/** Unbinds the currently bound renderbuffer.
	 */
	public void unbindRenderbuffer() {
		if (renderbufferBinding == null) {
			// already unbound
			return;
		}

		getGl().glBindRenderbuffer(GL.GL_RENDERBUFFER, 0);
		renderbufferBinding.release();
		renderbufferBinding = null;
	}

	/** Returns the renderbuffer currently bound.
	 */
	public GpuRenderbuffer getBoundRenderbuffer() {
		return renderbufferBinding;
	}

	// FRAMEBUFFERS:

	/** Returns the maximum number of color attachments.
	 */
	public int getMaxColorAttachments() {
		return maxColorAttachments;
	}

	/** Sets whether sRGB mode is enabled for the framebuffer.
	 */
	public void setFramebufferSRgbEnabled(boolean e) {
		framebufferSRgbEnabled = GpuObject.setStateEnabled(this, e, framebufferSRgbEnabled, GL3.GL_FRAMEBUFFER_SRGB);
	}

	/** Returns whether sRGB mode is enabled for the framebuffer.
	 */
	public boolean isFramebufferSRgbEnabled() {
		return framebufferSRgbEnabled;
	}

	/** Creates a framebuffer.
	 */
	public GpuFramebuffer createFramebuffer() {
		GpuFramebuffer ret = new GpuFramebuffer(this);
		ret.acquire();
		return ret;
	}

	/** Binds the given framebuffer to the given location.
	 */
	public void bindFramebuffer(int location, GpuFramebuffer framebuffer) {
		switch (location) {
			case GL3.GL_DRAW_FRAMEBUFFER:
				if (drawFramebufferBinding == framebuffer) {
					// already bound
					return;
				}

				getGl().glBindFramebuffer(location, framebuffer.getId());

				framebuffer.acquire();
				if (drawFramebufferBinding != null) {
					drawFramebufferBinding.release();
				}
				drawFramebufferBinding = framebuffer;

				break;
			case GL3.GL_READ_FRAMEBUFFER:
				if (readFramebufferBinding == framebuffer) {
					// already bound
					return;
				}

				getGl().glBindFramebuffer(location, framebuffer.getId());

				framebuffer.acquire();
				if (readFramebufferBinding != null) {
					readFramebufferBinding.release();
				}
				readFramebufferBinding = framebuffer;

				break;
			case GL.GL_FRAMEBUFFER:
				if (drawFramebufferBinding == framebuffer && readFramebufferBinding == framebuffer) {
					// already bound
					return;
				}

				getGl().glBindFramebuffer(location, framebuffer.getId());

				framebuffer.acquire();
				if (drawFramebufferBinding != null) {
					drawFramebufferBinding.release();
				}
				drawFramebufferBinding = framebuffer;

				framebuffer.acquire();
				if (readFramebufferBinding != null) {
					readFramebufferBinding.release();
				}
				readFramebufferBinding = framebuffer;

				break;
		}
	}

	/** Unbinds the currently bound renderbuffer from the given location.
	 */
	public void unbindFramebuffer(int location) {
		switch (location) {
			case GL3.GL_DRAW_FRAMEBUFFER:
				if (drawFramebufferBinding == null) {
					// already unbound
					return;
				}

				getGl().glBindFramebuffer(location, 0);

				if (drawFramebufferBinding != null) {
					drawFramebufferBinding.release();
				}
				drawFramebufferBinding = null;

				break;
			case GL3.GL_READ_FRAMEBUFFER:
				if (readFramebufferBinding == null) {
					// already unbound
					return;
				}

				getGl().glBindFramebuffer(location, 0);

				if (readFramebufferBinding != null) {
					readFramebufferBinding.release();
				}
				readFramebufferBinding = null;

				break;
			case GL.GL_FRAMEBUFFER:
				if (drawFramebufferBinding == null && readFramebufferBinding == null) {
					// already unbound
					return;
				}

				getGl().glBindFramebuffer(location, 0);

				if (drawFramebufferBinding != null) {
					drawFramebufferBinding.release();
				}
				drawFramebufferBinding = null;

				if (readFramebufferBinding != null) {
					readFramebufferBinding.release();
				}
				readFramebufferBinding = null;

				break;
			default:
				throw new IllegalArgumentException("Invalid framebuffer binding location");
		}
	}

	/** Returns the status of the framebuffer.
	 */
	public int checkFramebufferStatus(int location) {
		return getGl().glCheckFramebufferStatus(location);
	}

	/** Returns the framebuffer currently bound.
	 */
	public GpuFramebuffer getBoundFramebuffer(int location) {
		if (location == GL.GL_FRAMEBUFFER || location == GL3.GL_DRAW_FRAMEBUFFER) {
			return drawFramebufferBinding;
		} else if (location == GL3.GL_READ_FRAMEBUFFER) {
			return readFramebufferBinding;
		} else {
			throw new IllegalArgumentException("Invalid framebuffer binding location");
		}
	}

	// BUFFERS:

	/** Creates a buffer.
	 */
	public GpuBuffer createBuffer(GpuBuffer.BufferDescription desc) {
		GpuBuffer ret = new GpuBuffer(this, desc);
		ret.acquire();
		return ret;
	}

	/** Binds the given buffer to the location corresponding to its originally specified type.
	 */
	public void bindBuffer(GpuBuffer buffer) {
		bindBuffer(buffer.getType(), buffer);
	}

	/** Binds the given buffer to the given location.
	 */
	public void bindBuffer(int location, GpuBuffer buffer) {
		int loc = getBufferBindingLocation(location).ordinal();
		if (bufferBindingLocations[loc] == buffer) {
			// already bound
			return;
		}

		getGl().glBindBuffer(location, buffer.getId());

		buffer.acquire();
		if (bufferBindingLocations[loc] != null) {
			bufferBindingLocations[loc].release();
		}
		bufferBindingLocations[loc] = buffer;
	}

	/** Unbinds the currently bound buffer from the given location.
	 */
	public void unbindBuffer(int location) {
		int loc = getBufferBindingLocation(location).ordinal();
		if (bufferBindingLocations[loc] == null) {
			// already unbound
			return;
		}

		getGl().glBindBuffer(location, 0);

		bufferBindingLocations[loc].release();
		bufferBindingLocations[loc] = null;
	}

	/** Returns the buffer currently bound to the given location.
	 */
	public GpuBuffer getBoundBuffer(int location) {
		return bufferBindingLocations[getBufferBindingLocation(location).ordinal()];
	}

	// PROGRAMS:

	/** Creates a program.
	 */
	public GpuProgram createProgram(GpuProgram.ProgramDescription desc)
			throws GpuException {
		GpuProgram ret = new GpuProgram(this, desc);
		ret.acquire();
		return ret;
	}

	/** Binds the given program.
	 */
	public void bindProgram(GpuProgram program) {
		if (programBinding == program) {
			// already bound
			return;
		}

		getGl().glUseProgram(program.getProgramId());

		program.acquire();
		if (programBinding != null) {
			programBinding.onUnbind();
			programBinding.release();
		}
		programBinding = program;
	}

	/** Unbinds the currently bound program.
	 */
	public void unbindProgram() {
		if (programBinding == null) {
			// already unbound
			return;
		}

		getGl().glUseProgram(0);

		programBinding.onUnbind();
		programBinding.release();
		programBinding = null;
	}

	/** Returns the program currently bound.
	 */
	public GpuProgram getBoundProgram() {
		return programBinding;
	}

	// ATTRIBUTES:

	/** Returns the number of available attributes.
	 */
	public int getAttributeCount() {
		return attributes.length;
	}

	/** Returns the given attribute.
	 */
	public GpuAttribute getAttribute(int i) {
		return attributes[i];
	}

	// DRAW CALLS:

	/** Creates a draw state.
	 */
	public GpuDrawState createDrawState() {
		return new GpuDrawState(this);
	}

	/** Binds the draw state.
	 */
	public void bind(GpuDrawState newState) {
		newState.apply(drawState);
	}

	/** Retrieves the current draw state.
	 */
	public GpuDrawState getCurrentDrawState() {
		return new GpuDrawState(drawState);
	}

	/** Creates a draw call.
	 */
	public GpuDrawCall.DrawCall createDrawCall() {
		return new GpuDrawCall.DrawCall(this);
	}

	/** Creates an instanced draw call.
	 */
	public GpuDrawCall.InstancedDrawCall createInstancedDrawCall() {
		return new GpuDrawCall.InstancedDrawCall(this);
	}

	/** Creates an indexed draw call.
	 */
	public GpuDrawCall.IndexedDrawCall createIndexedDrawCall() {
		return new GpuDrawCall.IndexedDrawCall(this);
	}

	/** Creates an indexed offset draw call.
	 */
	public GpuDrawCall.IndexedOffsetDrawCall createIndexedOffsetDrawCall() {
		return new GpuDrawCall.IndexedOffsetDrawCall(this);
	}

	/** Creates an indexed instanced draw call.
	 */
	public GpuDrawCall.IndexedInstancedDrawCall createIndexedInstancedDrawCall() {
		return new GpuDrawCall.IndexedInstancedDrawCall(this);
	}

	/** Creates an indexed instanced offset draw call.
	 */
	public GpuDrawCall.IndexedInstancedOffsetDrawCall createIndexedInstancedOffsetDrawCall() {
		return new GpuDrawCall.IndexedInstancedOffsetDrawCall(this);
	}

	/** Creates an indexed ranged draw call.
	 */
	public GpuDrawCall.IndexedRangedDrawCall createIndexedRangedDrawCall() {
		return new GpuDrawCall.IndexedRangedDrawCall(this);
	}

	/** Creates an indexed ranged offset draw call.
	 */
	public GpuDrawCall.IndexedRangedOffsetDrawCall createIndexedRangedOffsetDrawCall() {
		return new GpuDrawCall.IndexedRangedOffsetDrawCall(this);
	}

	/** Creates a clear call.
	 */
	public GpuDrawCall.ClearCall createClearCall() {
		return new GpuDrawCall.ClearCall(this);
	}

	// current states
	private GpuBlendState blendState;
	private GpuDepthStencilState depthStencilState;
	private GpuRasterizerState rasterizerState;
	private GpuScissorState scissorState;
	private GpuViewportState viewportState;
	private GpuDrawBufferState drawBufferState;
	private int maxDrawBuffers;

	// array of texture image units
	private GpuTextureImageUnit[] textureImageUnits;
	private int activeTextureImageUnit;

	// renderbuffer binding
	private GpuRenderbuffer renderbufferBinding;

	// framebuffer bindings
	private GpuFramebuffer drawFramebufferBinding;
	private GpuFramebuffer readFramebufferBinding;
	private int maxColorAttachments;
	private boolean framebufferSRgbEnabled;

	// buffer bindings
	private enum BufferBindingLocation {
		ARRAY_BUFFER,
		COPY_READ_BUFFER,
		COPY_WRITE_BUFFER,
		ELEMENT_ARRAY_BUFFER,
		PIXEL_PACK_BUFFER,
		PIXEL_UNPACK_BUFFER,
		TEXTURE_BUFFER,
		TRANSFORM_FEEDBACK_BUFFER,
		UNIFORM_BUFFER
	}
	private static BufferBindingLocation getBufferBindingLocation(int loc) {
		switch (loc) {
			case GL.GL_ARRAY_BUFFER:
				return BufferBindingLocation.ARRAY_BUFFER;
			case GL3.GL_COPY_READ_BUFFER:
				return BufferBindingLocation.COPY_READ_BUFFER;
			case GL3.GL_COPY_WRITE_BUFFER:
				return BufferBindingLocation.COPY_WRITE_BUFFER;
			case GL.GL_ELEMENT_ARRAY_BUFFER:
				return BufferBindingLocation.ELEMENT_ARRAY_BUFFER;
			case GL3.GL_PIXEL_PACK_BUFFER:
				return BufferBindingLocation.PIXEL_PACK_BUFFER;
			case GL3.GL_PIXEL_UNPACK_BUFFER:
				return BufferBindingLocation.PIXEL_UNPACK_BUFFER;
			case GL3.GL_TEXTURE_BUFFER:
				return BufferBindingLocation.TEXTURE_BUFFER;
			case GL3.GL_TRANSFORM_FEEDBACK_BUFFER:
				return BufferBindingLocation.TRANSFORM_FEEDBACK_BUFFER;
			case GL3.GL_UNIFORM_BUFFER:
				return BufferBindingLocation.UNIFORM_BUFFER;
			default:
				throw new IllegalArgumentException("Invalid buffer binding location");
		}
	}

	private GpuBuffer[] bufferBindingLocations = new GpuBuffer[BufferBindingLocation.values().length];

	// program binding
	private GpuProgram programBinding;

	// array of attributes
	private GpuAttribute[] attributes;

	// draw state
	private GpuDrawState drawState;
}